package com.gitee.yusugar.core.util;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.yusugar.common.YuToolServiceException;
import lombok.*;
import lombok.experimental.Accessors;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * 判断一个 pojo 类里面的属性是否为 null 或 ""，前提是要有 getter 和 setter 方法。
 * 且 实体类 里的属性，最好定义为包装类。否则它会含有默认值，无法判断是否为 null，这样的结果将一直为 true
 *
 * @author Y-sugar
 */
public class ObjUtil {

    protected static final String GET = "get";

    /**
     * 判断对象里的字段是否为空
     *
     * @param obj               要判断的对象
     * @param isEmpty           为 true 时：若该类所有字段值都为空返回 true，若有一个或多个不为空返回 false.
     *                          为 false 时：若该类所有字段值都不为空返回 true，若有一个或多个为空返回 false
     * @param obtainParentCount 获取几级父类字段
     * @return 若该类所有字段值都不为空返回 true，若有一个或多个为空返回 false
     */
    private static boolean judgeFieldIsEmpty(Object obj, boolean isEmpty, int obtainParentCount) {
        if (obj == null)
            return isEmpty;

        List<Property> properties = objFieldToProps(obj, obtainParentCount);
        int totalSize = properties.size();

        long emptyCount = properties.stream().filter(Property::isEmpty).count();

        return isEmpty ? emptyCount == totalSize : emptyCount == 0;
    }

    /**
     * @param obj 要判断的对象
     * @return 若该类所有字段属性都不为空返回 true，若有一个或多个为空返回 false
     */
    public static boolean allFieldIsNotEmpty(Object obj, int obtainParentCount) {
        return judgeFieldIsEmpty(obj, false, obtainParentCount);
    }

    /**
     * @param obj 要判断的对象
     * @return 若该类所有字段属性都为空返回 true，若有一个或多个不为空返回 false
     */
    public static boolean allFieldIsEmpty(Object obj, int obtainParentCount) {
        return judgeFieldIsEmpty(obj, true, obtainParentCount);
    }

    /**
     * 判断对象里的某些字段是否为空
     *
     * @param obj               要判断的对象
     * @param isEmpty           为 true 时：若该类所有字段值都为空返回 true，若有一个或多个不为空返回 false.
     *                          为 false 时：若该类所有字段值都不为空返回 true，若有一个或多个为空返回 false
     * @param obtainParentCount 获取几级父类字段
     * @param fieldNameArr      要判断的字段
     * @return 结果
     */
    private static boolean judgeFieldIsEmpty(Object obj, boolean isEmpty, int obtainParentCount, String... fieldNameArr) {
        if (obj == null)
            return isEmpty;

        if (Arrays.stream(fieldNameArr).noneMatch(StrUtil::isNotBlank))
            return judgeFieldIsEmpty(obj, isEmpty, obtainParentCount);

        List<Property> properties = objFieldToProps(obj, obtainParentCount);
        int totalSize = fieldNameArr.length;

        long emptyCount = Arrays.stream(fieldNameArr).filter(name -> properties.stream().anyMatch(prop -> prop.getName().equals(name) && prop.isEmpty())).count();

        return isEmpty ? emptyCount == totalSize : emptyCount == 0;
    }

    /**
     * @param obj        要判断的对象
     * @param fieldsName 要判断的字段
     * @return 若该类所有字段值都不为空返回 true，若有一个或多个为空返回 false
     */
    public static boolean allFieldIsNotEmpty(Object obj, int obtainParentCount, String... fieldsName) {
        return judgeFieldIsEmpty(obj, false, obtainParentCount, fieldsName);
    }

    /**
     * @param obj        要判断的对象
     * @param fieldsName 要判断的字段
     * @return 若该类所有字段值都为空返回 true，若有一个或多个不为空返回 false
     */
    public static boolean allFieldIsEmpty(Object obj, int obtainParentCount, String... fieldsName) {
        return judgeFieldIsEmpty(obj, true, obtainParentCount, fieldsName);
    }

    /**
     * 只转换当前对象的字段
     *
     * @param obj 待转换对象
     * @return 转换成功的对象
     */
    public static List<Property> objFieldToProps(Object obj) {
        return objFieldToProps(obj, 0);
    }

    /**
     * 只转换当前对象和一级父级对象
     *
     * @param obj 待转换对象
     * @return 转换成功的对象
     */
    public static List<Property> objFieldAndFirstParentToProps(Object obj) {
        return objFieldToProps(obj, 1);
    }

    /**
     * 转换对象
     *
     * @param obj               待转换对象
     * @param obtainParentCount 获取几级父类字段
     * @return 转换成功的对象
     */
    public static List<Property> objFieldToProps(Object obj, int obtainParentCount) {
        List<Property> propertyList = new ArrayList<>();
        Class<?> cls = obj.getClass();

        Field[] fields = cls.getDeclaredFields();

        for (Field field : fields) {
            int fieldValue = field.getModifiers();
            if (Modifier.isStatic(fieldValue) || Modifier.isPublic(fieldValue) || Modifier.isFinal(fieldValue)) {
                continue;
            }

            Property prop = new Property();

            try {
                String name = field.getName();
                prop.setName(name).setSimpleType(field.getType().getSimpleName()).setType(field.getType());

                Object fieldVal = cls.getMethod(obtainGetMethod(name)).invoke(obj);

                prop.setData(fieldVal).setEmpty(fieldVal == null || fieldVal instanceof String && fieldVal.toString().trim().length() <= 0);
                propertyList.add(prop);
            } catch (Exception e) {
                throw new YuToolServiceException("字段转换异常，" + e.getMessage());
            }
        }

        if (obtainParentCount > 0) {
            Class<?> superclass = cls.getSuperclass();
            if (!superclass.isAssignableFrom(Object.class)) {
                List<Property> parentProps = objFieldToProps(JSONObject.parseObject(JSON.toJSONString(obj), superclass), --obtainParentCount);
                if (parentProps.size() > 0) {
                    propertyList.addAll(parentProps);
                }
            }
        }

        return propertyList;
    }


    /**
     * 获取 get 方法名称
     *
     * @param fieldName 字段名称
     * @return get 方法名称
     */
    protected static String obtainGetMethod(String fieldName) {
        return GET + toHeadUpperCase(fieldName);
    }

    /**
     * 将字段首字母转成大写并返回
     *
     * @param fieldName 字段名称
     * @return get 字段首字母转成大写并返回
     */
    protected static String toHeadUpperCase(String fieldName) {
        char headLetter = fieldName.charAt(0);
        return Character.isLowerCase(headLetter) ? (char) (headLetter - 32) + fieldName.substring(1) : fieldName;
    }

    @Getter
    @Setter
    @Accessors(chain = true)
    public static class Property {

        private String name;

        private Class<?> type;

        private String simpleType;

        private boolean empty;

        private Object data;
    }
}
